package com.example.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.mapper.CommonDistributedLockMapper;
import com.example.model.CommonDistributedLock;
import com.example.service.ICommonDistributedLockService;
import com.example.util.DISTRIBUTED_LOCK_ENUM;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.stereotype.Service;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Date;
import java.util.Enumeration;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * <p>
 * 数据库实现的简易锁
 * </p>
 *
 * @author mybatis_plus_generator
 * @since 2020-07-24
 */
@Service
@Slf4j
public class CommonDistributedLockServiceImpl extends ServiceImpl<CommonDistributedLockMapper, CommonDistributedLock> implements ICommonDistributedLockService {
    static AtomicBoolean isIpAddressInited = new AtomicBoolean();

    static String localAddress = null;

    @Override
    public boolean tryLock(String lockName, int expireDurationInSeconds) {
        QueryWrapper<CommonDistributedLock> wrapper = new QueryWrapper<>();
        wrapper.eq("lock_name", lockName);
        DateTime dateTime = new DateTime();
        wrapper.and(w -> {
            w.eq("state", CommonDistributedLock.UNLOCKED).or().le("expire_time", dateTime.toDate());
        });

        Date expireTime = dateTime.plusSeconds(expireDurationInSeconds).toDate();
        String owner = getOwner();
        Integer count = this.baseMapper.tryLock(expireTime, owner, wrapper);
        if (count == 0) {
            return false;
        }

        return true;
    }

    @Override
    public void initLock() {
        QueryWrapper<CommonDistributedLock> wrapper = new QueryWrapper<>();
        String lockName = DISTRIBUTED_LOCK_ENUM.SEND_CACHED_FEIGN_REQ_TO_REMOTE_SERVER.lockName;
        wrapper.eq("lock_name", lockName);
        int count = this.count(wrapper);
        if (count > 0) {
            // nothing
        } else {
            CommonDistributedLock lock = new CommonDistributedLock();
            lock.setLockId(2L);
            lock.setLockName(lockName);
            lock.setState(CommonDistributedLock.UNLOCKED);
            lock.setExpireTime(null);
            lock.setOwner(null);

            this.save(lock);
        }

    }

    @Override
    public void unlock(String lockName) {
        Integer count = this.baseMapper.unlock(lockName, getOwner());
        log.debug("unlock affected count of row:{}", count);
    }

    public static String getOwner() {
        String localMachineIp = getLocalMachineIp();
        return localMachineIp;
    }


    public static String getLocalMachineIp() {
        if (localAddress == null) {
            if (isIpAddressInited.compareAndSet(false, true)) {
                InetAddress localIPAddress = getLocalIPAddress();
                if (localIPAddress != null) {
                    localAddress = localIPAddress.getHostAddress();
                    return localAddress;
                }
                return null;
            }
        }

        return localAddress;
    }


    public static InetAddress getLocalIPAddress() {
        try {
            Enumeration<?> netInterfaces = NetworkInterface.getNetworkInterfaces();
            InetAddress inetAddress = null;
            while (netInterfaces.hasMoreElements()) {
                NetworkInterface ni = (NetworkInterface) netInterfaces.nextElement();
                Enumeration<?> e2 = ni.getInetAddresses();
                while (e2.hasMoreElements()) {
                    inetAddress = (InetAddress) e2.nextElement();
                    if (!inetAddress.isLoopbackAddress() && !inetAddress.getHostAddress().contains(":")) {
                        return inetAddress;
                    }
                }
            }
        } catch (Exception e) {
            log.error("getLocalIP error", e);
        }

        return null;
    }
}
